﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Entity;
using Kelper.Domain.Models;
using Kelper.Domain.Data.Abstract;

namespace Kelper.Domain.Data
{
    public class RecipeRepository : IRecipeRepository
    {
        private KelperDbContext db;

        public RecipeRepository(KelperDbContext db)
        {
            this.db = db;
        }

        public IQueryable<Recipe> Recipes
        {
            get
            {
                return db.Recipes
                    .Include(r => r.Author)
                    .Include(r => r.Category);
            }
        }

        public IEnumerable<RecipeSearchResult> Search(List<string> ingredients)
        {
            return Recipes
                .Where(r => r.Ingredients.Any(i => ingredients.Any(si => i.Name == si)))
                .Select(r => new RecipeSearchResult
                {
                    Recipe = r,
                    Priority = r.Ingredients.Select(i => i.Name).Intersect(ingredients).Count()
                    / (double)r.Ingredients.Count
                })
                .OrderByDescending(r => r.Priority)
                .ThenByDescending(r => r.Recipe.Ingredients.Count);
        }

        public IEnumerable<RecipeSearchResult> Search(string query)
        {
            HashSet<string> keywords =
                new HashSet<string>(
                    query.Split(new[] { ',', '.', ' ' }, StringSplitOptions.RemoveEmptyEntries)
                    .Where(k => k.Trim() != "")
                );

            return Recipes
                .Where(r => keywords.Any(k => r.Name.Contains(k)))
                .Select(r => new RecipeSearchResult
                {
                    Recipe = r,
                    Priority = keywords.Count(k => r.Name.Contains(k))
                })
                .OrderByDescending(r => r.Priority);
        }

        public bool Save(Recipe recipe)
        {
            // TODO: This method should be refactored to find a recipe first and
            // in that case assume that it is a recipe that should be edited

            Category category = db.Categories.FirstOrDefault(c => c.Name == recipe.Category.Name);
            User author = db.Users.FirstOrDefault(u => u.Username == recipe.Author.Username);
            if (category != null && author != null)
            {
                recipe.Author = author;
                recipe.Category = category;
                UpdateIngredients(recipe, recipe.Ingredients);

                if (recipe.ID == 0)
                    db.Recipes.Add(recipe);
                else
                {
                    Recipe modified = db.Recipes.FirstOrDefault(r => r.ID == recipe.ID);
                    modified.Ingredients.Clear();
                    db.SaveChanges();
                    UpdateIngredients(modified, recipe.Ingredients);
                    modified.Category = category;
                    modified.Text = recipe.Text;
                    db.Entry(modified).State = EntityState.Modified;
                }
                try
                {
                    db.SaveChanges();
                    return true;
                }
                catch { }
            }
            return false;
        }

        public bool Remove(int id)
        {
            Recipe result = db.Recipes.FirstOrDefault(r => r.ID == id);

            if (result != null)
            {
                List<Ingredient> removeIngredients = new List<Ingredient>();
                foreach (Ingredient i in result.Ingredients)
                    if (!db.Recipes.Any(r => r.ID != result.ID && r.Ingredients.Any(i2 => i2.Name == i.Name)))
                        removeIngredients.Add(i);

                foreach (Ingredient i in removeIngredients)
                    db.Ingredients.Remove(i);

                db.Recipes.Remove(result);
                db.SaveChanges();
            }

            return false;
        }

        private void UpdateIngredients(Recipe recipe, IEnumerable<Ingredient> ingredients)
        {
            List<Ingredient> dbIngredients = new List<Ingredient>();
            foreach (Ingredient ingredient in ingredients)
            {
                Ingredient ingredientInDb =
                    db.Ingredients.FirstOrDefault(i => i.Name == ingredient.Name);
                dbIngredients.Add(ingredientInDb == null ? ingredient : ingredientInDb);
            }
            recipe.Ingredients = dbIngredients;
        }
    }
}
